home *** CD-ROM | disk | FTP | other *** search
/ Clipart Warehouse 1 (Chestnut) / Clipart Warehouse.iso / utils / giftech / gifslow.pas < prev    next >
Pascal/Delphi Source File  |  1993-04-30  |  30KB  |  815 lines

  1. program gifslow;
  2.  
  3. {Written 1/16/88-1/19/88 by Jim Griebel. This software is experimental!
  4. USE AT YOUR OWN RISK. In the public domain. 'GIF' and 'Graphics Interchange
  5. Format' are trademarks of Compuserve, Inc., an H&R Block Company. 'Turbo
  6. Pascal' is a trademark of Borland International.}
  7.  
  8. {This is a short simple GIF reader/displayer for the EGA, adapted from
  9. GIFREAD, an earlier effort targeted on the Hercules. No provision is made
  10. for saving files or for scrolling in this program, which is intended as an
  11. example. This is the ultraslow version, pure high level}
  12.  
  13.  
  14. uses crt,dos;
  15.  
  16. type
  17.  
  18.     RasterArray = Array [0..63999] of byte;
  19.     RasterP = ^RasterArray;
  20.  
  21. var
  22.     GifFile:File of RasterArray;  {The input file}
  23.     GifStuff:RasterP;   {The heap array to hold it, raw}
  24.     Raster:RasterP;     {The raster data stream, unblocked}
  25.     Raster2:RasterP;    {More raster data stream if needed}
  26.     Regs:Registers;     {Turbo's predefined record}
  27.  
  28.     Byteoffset,         {Computed byte position in RASTER array}
  29.     Bitoffset           {Bit offset of next code in RASTER array}
  30.     :LongInt;
  31.  
  32.     Width,      {Read from GIF header, image width}
  33.     Height,     { ditto, image height}
  34.     LeftOfs,    { ditto, image offset from left}
  35.     TopOfs,     { ditto, image offset from top}
  36.     RWidth,     { ditto, raster width}
  37.     RHeight,    { ditto, raster height}
  38.     ClearCode,  {GIF clear code}
  39.     EOFCode,    {GIF end-of-information code}
  40.     OutCount,   {Decompressor output 'stack count'}
  41.     MaxCode,    {Decompressor limiting value for current code size}
  42.     Code,       {Value returned by ReadCode}
  43.     CurCode,    {Decompressor variable}
  44.     OldCode,    {Decompressor variable}
  45.     InCode,     {Decompressor variable}
  46.     FirstFree,  {First free code, generated per GIF spec}
  47.     FreeCode,   {Decompressor, next free slot in hash table}
  48.     GIFPtr,     {Array pointers used during file read}
  49.     RasterPtr,
  50.     XC,YC,      {Screen X and Y coords of current pixel}
  51.     Pindex,     {Index into screen save array}
  52.     ReadMask,   {Code AND mask for current code size}
  53.     I           {Loop counter, what else?}
  54.     :word;
  55.  
  56.  
  57.     Interlace,  {True if interlaced image}
  58.     NextRaster, {True if file > 64000 bytes}
  59.     ColorMap    {True if colormap present}
  60.     :Boolean;
  61.  
  62.     ch           {Utility}
  63.     :char;
  64.  
  65.     a,              {Utility}
  66.     Resolution,     {Resolution, read from GIF header}
  67.     BitsPerPixel,   {Bits per pixel, read from GIF header}
  68.     Background,     {Background color, read from GIF header}
  69.     ColorMapSize,   {Length of color map, from GIF header}
  70.     CodeSize,       {Code size, read from GIF header}
  71.     InitCodeSize,   {Starting code size, used during Clear}
  72.     FinChar,        {Decompressor variable}
  73.     Pass,           {Used by video output if interlaced pic}
  74.     BitMask,        {AND mask for data size}
  75.     R,G,B
  76.     :byte;
  77.  
  78.  
  79.     {The hash table used by the decompressor}
  80.     Prefix: Array [0..4095] of word;
  81.     Suffix: Array [0..4095] of byte;
  82.  
  83.     {An output array used by the decompressor}
  84.     Outcode:Array [0..1024] of byte;
  85.  
  86.     {The color map, read from the GIF header}
  87.     Red,Green,Blue: array [0..255] of byte;
  88.  
  89.     {The EGA palette, derived from the color map}
  90.     Palette: Array [0..255] of byte;
  91.  
  92.     {Strings to hold the filenames}
  93.     FileString:String [80];
  94.  
  95.  
  96. Const
  97.  
  98.     MaxCodes: Array [0..9] of Word = (4,8,16,$20,$40,$80,$100,$200,$400,$800);
  99.  
  100.     CodeMask:Array [1..4] of byte= (1,3,7,15);
  101.  
  102.     PowersOf2: Array [0..8] of word=(1,2,4,8,16,32,64,128,256);
  103.  
  104.     Masks: Array [0..9] of Integer = (7,15,$1f,$3f,$7f,$ff,$1ff,$3ff,$7ff,$fff);
  105.  
  106.     Rastersize:Word = 64000;
  107.  
  108.  
  109. {This procedure checks to be sure we've got enough heap for the array
  110. we're trying to allocate, then allocates same. If there isn't enough
  111. heap available, we exit with an error}
  112.  
  113. Procedure AllocMem (Var P:RasterP);
  114.  
  115. Var ASize:Longint;
  116.  
  117. Begin
  118.      ASize:=MaxAvail;
  119.      If ASize<RasterSize then
  120.         Begin
  121.              Textmode (15);
  122.              Writeln ('Insufficient memory available!');
  123.              Halt;
  124.         End
  125.         Else
  126.         Getmem (P,RasterSize);
  127. End;
  128.  
  129.  
  130. {Mimics a file read of a single byte, reading from the input record rather
  131. than the file itself. If you wish to change back to a file of byte rather
  132. than using the faster read of the record, you can modify this routine to
  133. read directly from the file. This is simpler but slower}
  134.  
  135. Function Getbyte:Byte;
  136.  
  137.   Begin
  138.        If GIFPtr=RasterSize then Exit;
  139.        Getbyte:=GIFStuff^[GIFPtr];
  140.        GIFPtr:=Succ(GIFPtr);
  141.   End;
  142.  
  143. {Reads two bytes, to get a word value}
  144.  
  145. Function Getword:Word;
  146.  
  147. Var A,B:Byte;
  148.  
  149. Begin
  150.      A:=Getbyte;
  151.      B:=Getbyte;
  152.      Getword:=A+(256*B);
  153. End;
  154.  
  155.  
  156.  
  157. {Mimic reading in the raster data. Unblock it into a single large array
  158. to save having to do this as we go, which makes life a lot simpler for
  159. the rest of the program. We cope here with files larger than 64000 bytes by
  160. doing another read from the input file, and by creating a second RASTER
  161. array if necessary to hold the excess unblocked data}
  162.  
  163. Procedure ReadRaster;
  164.  
  165. Var BlockLength:Byte;
  166.     I,IOR:Integer;
  167.  
  168. Begin
  169.    RasterPtr:=0;
  170.    Repeat
  171.    BlockLength:=Getbyte;
  172.      For I:=0 to Blocklength-1 do
  173.        Begin
  174.          If Gifptr = RasterSize then
  175.             Begin
  176.                  {$I-}
  177.                  Read (GIFFile,GIFStuff^);
  178.                  {$I+}
  179.                  IOR:=IOResult;
  180.                  GIFPtr:=0;
  181.             End;
  182.          If not Nextraster then
  183.                   Raster^[RasterPtr]:=Getbyte else
  184.                          Raster2^[RasterPtr]:=Getbyte;
  185.          RasterPtr:=Succ (RasterPtr);
  186.          If RasterPtr=RasterSize then
  187.          Begin
  188.             NextRaster:=True;
  189.             Rasterptr:=0;
  190.             AllocMem (Raster2);
  191.          End;
  192.        End;
  193.    Until Blocklength=0;
  194. End;
  195.  
  196.  
  197. {Fetch the next code from the raster data stream. The codes can be any
  198. length from 3 to 12 bits, packed into 8-bit bytes, so we have to maintain
  199. our location in the Raster array as a BIT offset. We compute the byte offset
  200. into the raster array by dividing this by 8, pick up three bytes, compute
  201. the bit offset into our 24-bit chunk, shift to bring the desired code to
  202. the bottom, then mask it off and return it. If the unblocked raster data
  203. overflows the original RASTER array, we switch to the second one}
  204.  
  205. Procedure ReadCode;
  206.  
  207. Var RawCode:LongInt;
  208.     A,B:Word;
  209.  
  210.  
  211. Begin
  212.      ByteOffset:=BitOffset div 8;
  213.  
  214. {Pick up our 24-bit chunk}
  215.  
  216.      A:=Raster^[Byteoffset]+(256*Raster^[ByteOffset+1]);
  217.      If CodeSize>=8 then
  218.      Begin
  219.      B:=Raster^[Byteoffset+2];
  220.      RawCode:=A+(65536*B);
  221.      End
  222.      Else Rawcode:=A;
  223.  
  224. {Doing the above calculation as a single statement, i.e.
  225. Rawcode:=Raster^[Byteoffset]+(256*Raster^[Byteoffset+1])+
  226.          (65536*Raster[Byteoffset+2])
  227. sometimes returns incorrect results. This may or may not be a bug.}
  228.  
  229.  
  230.      RawCode:=RawCode shr (BitOffset mod 8);
  231.      Code:=RawCode and ReadMask;
  232.  
  233. {Cope with overflow of the first RASTER array}
  234.  
  235.      If (Nextraster) and (Byteoffset>=63000) then
  236.         Begin
  237.              Move (Raster^[Byteoffset],Raster^[0],RasterSize-Byteoffset);
  238.              Move (Raster2^[0],Raster^[RasterSize-Byteoffset],63000);
  239.              Bitoffset:=Bitoffset mod 8;
  240.              FreeMem (Raster2,RasterSize);
  241.         End;
  242.  
  243.      BitOffset:=BitOffset+CodeSize;
  244.  
  245. End;
  246.  
  247.  
  248. Procedure AddToPixel (Index:Byte);
  249.  
  250.  
  251. Begin
  252.  
  253.      Regs.AH:=12;
  254.      Regs.AL:=Index;
  255.      Regs.CX:=XC;
  256.      Regs.DX:=YC;
  257.      Intr ($10,Regs);
  258.  
  259. {Update the X-coordinate, and if it overflows, update the Y-coordinate}
  260.  
  261.      XC:=Succ (XC);
  262.      If XC=Width then
  263.  
  264. {If a non-interlaced picture, just increment YC to the next scan line. If
  265. it's interlaced, deal with the interlace as described in the GIF spec. Put
  266. the decoded scan line out to the screen if we haven't gone past the bottom
  267. of it}
  268.  
  269.         Begin
  270.  
  271.         XC:=0;
  272.         If not Interlace then YC:=Succ (YC) else
  273.             Begin
  274.                Case Pass of
  275.                0: Begin
  276.                   YC:=YC+8;
  277.                   If YC>=Height then
  278.                   Begin
  279.                      Pass:=Succ(Pass);
  280.                      YC:=4;
  281.                   End;
  282.                   End;
  283.                1: Begin
  284.                   YC:=YC+8;
  285.                   If YC>=Height then
  286.                      Begin
  287.                        Pass:=Succ(Pass);
  288.                        YC:=2;
  289.                      End;
  290.                   End;
  291.                2: Begin
  292.                   YC:=YC+4;
  293.                   If YC>=Height then
  294.                      Begin
  295.                           Pass:=Succ(Pass);
  296.                           YC:=1;
  297.                      End;
  298.                   End;
  299.                3: Begin
  300.                   YC:=YC+2;
  301.                   End;
  302.                End;    {Case}
  303.             End;  {If interlace}
  304.         End;
  305.  
  306. End;
  307.  
  308. {Use the BIOS functions to set up the EGA. This avoids dependence on Turbo's
  309. GRAPH package and the necessity to keep .BGI files with the executable}
  310.  
  311. Procedure InitEGA;
  312.  
  313. Begin
  314.  
  315.  
  316. {Set EGA graphics mode}
  317.  
  318.    Regs.AX:=$0010;
  319.    Intr ($10,Regs);
  320.  
  321. {Set the palette}
  322.  
  323.    Regs.AX:=$1002;
  324.    Regs.DX:=Ofs (Palette);
  325.    Regs.ES:=Seg (Palette);
  326.    Intr ($10,Regs);
  327.  
  328. End;
  329.  
  330.  
  331. {Determine the palette value corresponding to the GIF colormap intensity
  332. value.}
  333.  
  334. Procedure DetColor (Var PValue:Byte;MapValue:Byte);
  335.  
  336. Var Local:Byte;
  337.  
  338. Begin
  339.      PValue:=MapValue div 64;
  340.      If PValue=1 then PValue:=2 else
  341.      If PValue=2 then PValue:=1;
  342. End;
  343.  
  344. {Set the key variables to
  345. their necessary initial values.}
  346.  
  347. Procedure ReInitialize;
  348. Begin
  349.      XC:=0;          {X and Y screen coords back to home}
  350.      YC:=0;
  351.      Pass:=0;        {Interlace pass counter back to 0}
  352.      Bitoffset:=0;   {Point to the start of the raster data stream}
  353.      GIFPtr:=0;      {Mock file read pointer back to 0}
  354. End;
  355.  
  356. {React to GIF clear code, or reset GIF decompression values back to their
  357. initial state when restarting.}
  358.  
  359. Procedure DoClear;
  360.  
  361.     Begin
  362.       CodeSize:=InitCodeSize;
  363.       MaxCode:=MaxCodes [CodeSize-2];
  364.       FreeCode:=FirstFree;
  365.       ReadMask:=Masks [CodeSize-3];
  366.     End;
  367.  
  368. Begin    {the main program}
  369.  
  370. {Initialize a bunch of variables}
  371.  
  372.      ReInitialize;         {Initialize common vars}
  373.      Nextraster:=False;    {Over 64000 flag off}
  374.  
  375. {Get memory for the raster data array, and the input file data array}
  376.  
  377.      AllocMem (Raster);
  378.      AllocMem (GIFStuff);
  379.  
  380. {Prompt the user for the filename}
  381.  
  382.      Write ('Filename: ');
  383.      Readln (Filestring);
  384.  
  385.  
  386. {Open the file}
  387.  
  388. {$I-}
  389.      Assign (giffile,FileString);
  390.      Reset (giffile);
  391. {$I+}
  392.  
  393. {Cope with I/O error should one occur}
  394.  
  395.      I:=IOResult;
  396.      If I<>0 then
  397.         Begin
  398.              Writeln ('Error opening file ',FileString,'. Press any key ');
  399.              Readln;
  400.              Exit;
  401.         End;
  402.  
  403. {Read in the GIF file. Reading it as one big hunk rather than N bytes results
  404. in far faster disk I/O; see user notes. Error checking is turned off in
  405. order to avoid 'attempt to read past EOF' errors. If the file does not exist,
  406. this will be detected at RESET}
  407.  
  408.      Writeln ('Reading . . . ');
  409. {$I-}
  410.      Read (GIFFile,GIFStuff^);
  411. {$I+}
  412.  
  413. {Note that 4.0 requires this assignment, or else if an error results (as it
  414. will if the file is smaller than 64000 bytes) no I/O will be allowed for
  415. the remainder of the run}
  416.  
  417. I:=IOResult;
  418.  
  419. {Deal with the GIF header. Start by checking the GIF tag to make sure this
  420. is a GIF file}
  421.  
  422.      FileString:='';
  423.      for i:=1 to 6 do
  424.      Begin
  425.          FileString:=FileString+chr(Getbyte);
  426.      End;
  427.      If FileString<>'GIF87a' then
  428.         Begin
  429.              Writeln ('Not a GIF file, or header read error. Press any key ');
  430.              Readln;
  431.              Exit;
  432.         End;
  433.  
  434. {Get variables from the GIF screen descriptor}
  435.  
  436.      RWidth:=Getword;         {The raster width and height}
  437.      RHeight:=Getword;
  438.      {Get the packed byte immediately following and decode it}
  439.      B:=Getbyte;
  440.      If B and $80=$80 then Colormap:=True else Colormap:=False;
  441.      Resolution:=B and $70 shr 5 +1;
  442.      BitsPerPixel:=B and 7 +1;
  443.      If BitsPerPixel=1 then I:=2 else I:=1 shl BitsPerPixel;
  444.      Write ('Colors: ',I);
  445.      BitMask:=CodeMask [BitsPerPixel];
  446.      Background:=Getbyte;
  447.      B:=Getbyte;         {Skip byte of 0's}
  448.  
  449. {Compute size of colormap, and read in the global one if there. Compute
  450. values to be used when we set up the EGA palette}
  451.  
  452.      ColorMapSize:=1 shl BitsPerPixel;
  453.      If Colormap then
  454.      Begin
  455.      For I:=0 to ColorMapSize-1 do
  456.      Begin
  457.          Red [I]:=Getbyte;
  458.          Green [I]:=Getbyte;
  459.          Blue [I]:=Getbyte;
  460.          DetColor (R,Red[I]);
  461.          DetColor (G,Green [I]);
  462.          DetColor (B,Blue [I]);
  463.          Palette [I]:=B and 1+(2*(G and 1))+(4*(R and 1))+(8*(B div 2))+(16*(G div 2))+(32*(R div 2));
  464.      End;
  465.      Writeln;
  466.      Palette [16]:=Background;
  467.      End;
  468.  
  469. {Now read in values from the image descriptor}
  470.  
  471.      B:=Getbyte;  {skip image seperator}
  472.      Leftofs:=Getword;
  473.      Topofs:=Getword;
  474.      Width:=Getword;
  475.      Writeln ('Width: ',Width);
  476.      Height:=Getword;
  477.      Writeln ('Height: ',Height);
  478.      A:=Getbyte;
  479.      If A and $40=$40 then Interlace:=True else Interlace:=False;
  480.  
  481.  
  482. {Note that we ignore the possible existence of a local color map. I've yet
  483. to encounter an image that had one, and the spec says it's defined for
  484. future use. This could lead to an error reading some files}
  485.  
  486. {Start reading the raster data. First we get the intial code size}
  487.  
  488.      Codesize:=Getbyte;
  489.  
  490. {Compute decompressor constant values, based on the code size}
  491.  
  492.      ClearCode:=PowersOf2 [Codesize];
  493.      EOFCode:=ClearCode+1;
  494.      FirstFree:=ClearCode+2;
  495.      FreeCode:=FirstFree;
  496.  
  497. {The GIF spec has it that the code size is the code size used to compute the
  498. above values is the code size given in the file, but the code size used in
  499. compression/decompression is the code size given in the file plus one.}
  500.  
  501.      Codesize:=Succ (Codesize);
  502.      InitCodeSize:=Codesize;
  503.      Maxcode:=Maxcodes [Codesize-2];
  504.      ReadMask:=Masks [Codesize-3];
  505.  
  506. {Read the raster data. Here we just transpose it from the GIF array to the
  507. Raster array, turning it from a series of blocks into one long data stream,
  508. which makes life much easier for ReadCode}
  509.  
  510.      Writeln ('Unblocking');
  511.      ReadRaster;
  512.  
  513. {Get ready to do the actual read/display. Free up the heap used by the
  514. GIF array since we don't need it any more, and if the user wants to save,
  515. reclaim it for the Picture array}
  516.  
  517.      FreeMem (GIFStuff,RasterSize);
  518.      OutCount:=0;
  519.  
  520. {Set up the EGA}
  521.  
  522.      InitEGA;
  523.  
  524. {Decompress the file, continuing until you see the GIF EOF code. One
  525. obvious enhancement is to add checking for corrupt files here.}
  526.  
  527.    Repeat
  528.  
  529.      {Get the next code from the raster array}
  530.  
  531.           ReadCode;
  532.  
  533.           If Code <> EOFCode then
  534.           Begin
  535.  
  536.      {Clear code sets everything back to its initial value, then reads
  537.       the immediately subsequent code as uncompressed data.}
  538.  
  539.             If Code = ClearCode then
  540.                Begin
  541.                  DoClear;
  542.                  ReadCode;
  543.                  CurCode:=Code;
  544.                  OldCode:=Code;
  545.                  FinChar:=Code and BitMask;
  546.                  AddToPixel (FinChar);
  547.                End
  548.                Else
  549.  
  550.      {If not a clear code, then must be data: save same as CurCode and InCode}
  551.  
  552.                Begin
  553.                 CurCode:=Code;
  554.                 InCode:=Code;
  555.  
  556.      {If greater or equal to FreeCode, not in the hash table yet; repeat
  557.       the last character decoded}
  558.  
  559.                 If Code>=FreeCode then
  560.                   Begin
  561.                     CurCode:=OldCode;
  562.                     OutCode [OutCount]:=FinChar;
  563.                     OutCount:=Succ (OutCount);
  564.                   End;
  565.  
  566.      {Unless this code is raw data, pursue the chain pointed to by CurCode
  567.      through the hash table to its end; each code in the chain puts its
  568.      associated output code on the output queue.}
  569.  
  570.                 If CurCode>BitMask then
  571.                    Repeat
  572.                      OutCode [OutCount]:=Suffix [CurCode];
  573.                      OutCount:=Succ (OutCount);
  574.                      CurCode:=Prefix [CurCode];
  575.                    Until CurCode<=BitMask;
  576.  
  577.       {The last code in the chain is treated as raw data.}
  578.  
  579.                FinChar:=CurCode and BitMask;
  580.                OutCode [OutCount]:=FinChar;
  581.                OutCount:=Succ (OutCount);
  582.  
  583.      {Now we put the data out to the using routine. It's been stacked
  584.       LIFO, so deal with it that way}
  585.  
  586.                For I:=OutCount-1 downto 0 do
  587.                     AddToPixel (Outcode [I]);
  588.  
  589.       {Make darned sure OutCount gets set back to start}
  590.  
  591.                OutCount:=0;
  592.  
  593.       {Build the hash table on-the-fly. No table is stored in the file.}
  594.  
  595.                Prefix [FreeCode]:=OldCode;
  596.                Suffix [FreeCode]:=FinChar;
  597.                OldCode:=InCode;
  598.  
  599.      {Point to the next slot in the table. If we exceed the current MaxCode
  600.       value, increment the code size unless it's already 12. If it is, do
  601.       nothing: the next code decompressed better be CLEAR}
  602.  
  603.                FreeCode:=Succ (FreeCode);
  604.                If FreeCode>=MaxCode then
  605.                 Begin
  606.                   If CodeSize < 12 then
  607.                   Begin
  608.                      CodeSize:=Succ (CodeSize);
  609.                      MaxCode:=MaxCode*2;
  610.                      ReadMask:=Masks [CodeSize-3];
  611.                   End;
  612.                 End;
  613.                End {not Clear};
  614.  
  615.                If Keypressed then
  616.                   Begin
  617.                        Ch:=Readkey;
  618.                        If Ch=#27 then
  619.                           Begin
  620.                                Textmode (15);
  621.                                Exit;
  622.                           End;
  623.                   End;
  624.             End; {not EOFCode}
  625.        Until Code=EOFCode;
  626.  
  627.        Writeln (^G); {signals whole picture decoded}
  628.  
  629.     {Read one key, then pack it in}
  630.  
  631.     Ch:=Readkey;
  632.  
  633.     Textmode (15);                 {Back to text}
  634.     Close (GifFile);
  635.     FreeMem (Raster,RasterSize);
  636.  
  637. End.
  638. -------- end of PASCAL Source file (file name is GIFSLOW.PAS) --------
  639. -------- start of User Notes (file name is USER.NOT) --------
  640. WHAT YOU GET
  641.  
  642.      The .ARC file you got should contain the following files:
  643.  
  644.      This USER.NOT file
  645.      GIFSLOW.PAS
  646.      GIFEGA.PAS
  647.      NLZW.ASM
  648.      READRAST.ASM
  649.      SCROLL.ASM
  650.      SCRSAVE.ASM
  651.      GIFSLOW.EXE
  652.      GIFEGA.EXE
  653.  
  654.      These make up the source and executable for two GIF reader/displayers
  655. written in Turbo Pascal 4.0 and MASM compatible assembly language. GIFSLOW
  656. is written entirely in Turbo to lay out the techniques being used in a form
  657. more accessible than assembly language, and boy, does it live up to its
  658. name -- you can go have a short beer while the program is decoding and
  659. displaying a single picture. GIFEGA is much faster -- it takes about 15-30
  660. seconds to display a picture depending on its complexity -- and adds a
  661. directory display, scrolling, and a file save feature. The guts of GIFEGA
  662. are in assembly-language external files, which I've tuned to make it as
  663. fast as I can manage. The principal reason for putting up these amateur
  664. works is to provide source code for LZW decompression, which seems to be a
  665. stumbling block for many who'd otherwise be writing their own GIF readers.
  666.      Using the program is pretty straightforward. With GIFSLOW all you do
  667. is type in the filename at the prompt. If you get tired of waiting for the
  668. picture to fully display, hitting ESC will get you out of it. GIFEGA is a
  669. little more sophisticated. The program will display a list of eligible
  670. files (files with the extension '.GIF') in the current directory. You're
  671. then prompted for a filename. Just hitting ENTER at this point gets you out
  672. of the program. Type in a single '\' character and the program will prompt
  673. you for a new path name, then switch to that new path and display a new
  674. list of files. Type in the filename you'd like to see displayed (you don't
  675. have to add the .GIF extension), and the program will display it. When the
  676. display is done, the computer will beep. If the picture is larger
  677. vertically than the screen, you can then scroll it by using the Up and Down
  678. Arrows and the Home and End keys. The Up and Down arrow keys move the
  679. picture in increments of ten scan lines at a time. The Home key moves to
  680. the top of the picture, and the End key moves to the bottom. Hitting ESC
  681. gets you out of the display and produces a prompt asking you to hit the
  682. space bar if you want to save the file as an EGA Paint compatible .SCR
  683. file. Hit the space bar and you'll save the file with the same pathname and
  684. filename but the extension .SCR. That's all there is to it.
  685.  
  686. HOW THE PROGRAM WORKS
  687.  
  688.      While the operation of the program is pretty fully explained in the
  689. comments, a brief sketch of its operation and a few notes on the techniques
  690. used are probably useful. The program starts by getting and sorting the
  691. directory list, displaying it, and then getting the filename from the user.
  692. The file is then read in, and it's worth spending a few moments on how we
  693. do this.
  694.      Since a GIF file has a header of no fixed length, the obvious way to
  695. treat it is as a file of byte. The problem is that reading long files a
  696. byte at a time in Turbo is a very slow process; if we cheat the compiler a
  697. bit by claiming that the file is really a single large data structure, and
  698. try to read it all in one fell swoop, we can speed matters up enormously.
  699.      This is what both programs do. The file is declared as a RasterArray,
  700. a one-dimensioned array of 64,000 bytes, which is maintained on the heap.
  701. We try to read this big data structure from the file. This demands that we
  702. turn off I/O checking during the read (otherwise if the file is smaller
  703. than 64,000 bytes an error will occur) and call IORESULT after the read (or
  704. else Turbo will refuse to do any more I/O). If the file is larger than
  705. 64,000 bytes, this will be detected and dealt with later.
  706.      Now we have the whole file, or most of it, in memory, and we have to
  707. read the header. This is done by the functions GetByte and GetWord, which
  708. mimic a file read of a single byte or of a word. We read the necessary
  709. variables described in the GIF spec from the file, and compute some more
  710. based on those. As described in 'Problems and Limitations' below we don't
  711. cope with absolutely every possibility outlined in the GIF spec, but I've
  712. yet to have the program fail on this account (although making the program
  713. conform completely to the spec is one obvious enhancement). We also read
  714. and set up the color palette. At this point, if it's a 256-color picture,
  715. we do an extremely crude fix to make the picture display in 16 colors with
  716. (hopefully) some remote resemblance to the original. This is done by
  717. arbitrarily declaring the first 16 colors in the palette to be the EGA
  718. palette, then resetting the color values above 16 to their nearest
  719. equivalents below 16. This could be improved on, and I'm open to
  720. suggestions as to how.
  721.      Once we have the necessary variables and constants all set up, we're
  722. ready to start decompressing. The first step in this is to unblock the
  723. whole data stream -- that is, to turn the data stream from a series of 255-
  724. byte or less blocks into one long stream of data. This may seem to be an
  725. unnecessary step; a lot of GIF readers seem to unblock the data stream as
  726. they go, but if we do that then each and every time we read a code we have
  727. to check to see if we're near the end of the current block, and if so, move
  728. the tail end of that block up to the front of the read buffer, unblock and
  729. add on the next block, and recompute BITOFFSET to point back to the start
  730. of the buffer. This makes for an awful lot of checking and computation
  731. repeated God knows how many times. Unblocking the data stream beforehand
  732. makes the READCODE routine much simpler, whether in high level or in
  733. assembly, and faster as well. (And why Compuserve decided to block the durn
  734. thing beats me: tradition?)
  735.      During unblocking we also cope with files larger than 64,000 bytes.
  736. The logic of this is simple enough: if we run out of GIFSTUFF during
  737. unblocking, we allocate memory for another RASTER array, do another read
  738. from the file, and put the rest of the data stream into this second RASTER
  739. array. During decoding, if this second array exists, we check to see if
  740. we're near the end of the first one. If so, we move the end of the first
  741. one up to the front of it, copy the contents of the second RASTER array
  742. into the first, reset BITOFFSET back to the start of the array, and keep
  743. on. This is just what other readers do on a block-by-block basis, but done
  744. on a larger scale, and only once. You can see this happen on a file bigger
  745. than 64,000 bytes: there'll be a 'hiccup' when the arrays are readjusted.
  746. This is faster than waiting and doing a second file read during the actual
  747. decompress/display, but as a consequence the program uses a lot of memory:
  748. as much as 192K over and above the size of the program at peak.
  749.      Once we have our data stream unblocked we can unravel the LZW
  750. decompression. This, frankly, I don't understand too well, but what comes
  751. out of the decompression cycle are pixels ready to write to the EGA screen.
  752. GIFSLOW does this a pixel at a time using a BIOS call; GIFEGA does this a
  753. scan line at a time, writing directly to EGA memory. We take advantage of
  754. the fact that the EGA's memory will hold a 640x480 picture (even if the
  755. card will only display 640x350) to get the whole picture into EGA memory,
  756. so that when we're done we can grab the four bit planes and save them in
  757. new heap arrays. We can then use those heap arrays to scroll the picture or
  758. save it as an EGA Paint-compatible .SCR file (which consists of the header
  759. followed by the four bit planes in order 0,2,1,3). Scrolling is done only
  760. if the picture is larger than the screen, and is done by using Write Mode 0
  761. to move the bitplanes from the heap arrays back into the EGA memory,
  762. starting at an an offset into the bitplane arrays determined by the Turbo
  763. variable ROLL. Originally the scrolling routine moved the bitplanes one at
  764. a time and all at a time, but this led to a 'rainbow' effect with some
  765. pictures (you could see the bitplanes being moved) and the routine was
  766. changed to move a scan line from each bitplane at a time, which got rid of
  767. the problem but allows you to see the scroll in action as the picture
  768. ripples down the screen. This could doubtless be improved.
  769.  
  770. LIMITATIONS AND KNOWN PROBLEMS
  771.  
  772.      The program deviates somewhat from the letter of the GIF spec, as
  773. follows:
  774.      No provision is made for a default colormap if a global one is not
  775. supplied.
  776.      No provision is made for dealing with a local colormap.
  777.      No provision is made for dealing with GIF enhancements, nor with
  778. multiple images in the same file.
  779.      While all these are deviations from the strict spec, I've yet to have
  780. the program fail on that account.
  781.      The program doesn't cope well with 256-color images. This is doubtless
  782. fixable (and one obvious area for improvement) but I haven't spent much
  783. time on it.
  784.      No provision is made in the program for modes other than vanilla EGA
  785. (640x350x16). One consequence of this is that though the save routine can
  786. write .SCP (640x480) files as well as .SCR files, this feature is
  787. permanently turned off in the program as it stands. This can be fixed, if
  788. you have an EGA that will handle more resolution, by changing the typed
  789. constant EGAHeight to 480.
  790.      The program doesn't work with GRABEGA; the files produced seem to have
  791. the wrong palette setting. Since GRABEGA works with other GIF readers the
  792. bug would seem to be in GIFEGA, but I don't know where, and since the
  793. program incorporates a .SCR save routine I'm not looking too hard.
  794.  
  795.      Some obvious areas of enhancement would include: more comprehensive
  796. error checking and recovery (at present the program will lock up or
  797. otherwise die if there's an error in the GIF data stream; detecting these
  798. is tough), printing, a sexier directory display with moving highlights,
  799. more save file formats, etc. More professional programmers would doubtless
  800. like more modularity and structure, less reliance on globals, and so on, in
  801. the code; anyone is welcome to dive in and improve the program any way he
  802. or she can.
  803.  
  804.      I hope you enjoy the program; I had fun writing it, and I hope you
  805. have fun with it. The code is placed in the public domain, since it's based
  806. on the work of others who were generous enough to place their code in the
  807. public domain; chief among these is Tom Pfau of DEC, who wrote the public
  808. domain file unsqueezer LZDCMP, on which this program is based. While I've
  809. claimed no copyright and you can do what you like with the code, it's
  810. intended for the use of individuals for their own enjoyment, and I ask that
  811. you don't use it in any program transferred for a fee.
  812.  
  813. -------- end of User Notes (file name is USER.NOT) --------
  814.  
  815.